iT邦幫忙

2025 iThome 鐵人賽

DAY 21
0
Cloud Native

EKS in Practice:IaC × GitOps 實戰 30 天系列 第 21

[Day 21] GitOps 的最後一哩路:CI/CD Pipeline 與 Image 自動更新

  • 分享至 

  • xImage
  •  

前言

在 Day 20 我們介紹了 KEDA,談到如何根據事件驅動自動調整 Pod 的數量,進一步提升叢集的資源彈性。這其實是一個里程碑:代表我們已經把基礎設施的骨架搭建完畢,從基礎資源(Karpenter)、流量入口(Ingress NGINX、Load Balancer Controller)、DNS(ExternalDNS)、到監控告警(Prometheus + Grafana + Alertmanager)都準備就緒。

因此今天我們會回到另一個環節:Code Delivery,畢竟再完美的基礎設施,如果應用程式不能快速、安全、持續地交付,也只是個空架子。Day 21 的主題就是把我們的 CI/CD pipeline 講清楚,看看如何讓程式碼從 Git commit 開始,一路走到映像檔建置,再交給 ArgoCD 進行部署,形成完整的 GitOps 閉環

為什麼要這樣設計 Pipeline?

我們的 pipeline 採用 GitLab CI,分成兩個主要的 stage:testbuild。這樣設計的原因是希望在流程的早期就盡快攔截錯誤,而不是到最後推送映像檔的時候才發現。

test 階段,我們做三件事:

  1. 用 Kaniko 嘗試 build 但不推送,只確認 Dockerfile 是否能夠正確被 build 成 image,並且執行 entrypoint 或 cmd 所指定的測試 endpoint。
  2. 針對基礎設施程式碼做 lint,例如 terraform fmttflint
  3. 做安全檢查,像是用 Spectral 掃描敏感資訊。

這樣的好處是,如果一個 MR 改壞了 Dockerfile、Terraform、或引入了敏感字串,問題能在 merge 之前就被阻止,避免浪費後續的 runner 資源。

到了 build 階段,我們才真正生成並推送映像檔。這裡我們嚴格依賴 tag 規則 來決定要建置哪個服務、哪個版本。例如:

dev/frontend/1.2.3
staging/backend/2.0.0

透過這種設計,開發者不需要修改 YAML,也不需要自己手動標記映像檔,只要打對 tag,Pipeline 就會自動把結果推送到正確的 ECR repo。這樣的規則化,讓 CI/CD 不會隨人員習慣而變動,降低了錯誤風險,也更符合 GitOps「宣告式」的精神。

我們曾經用 buildx,最後換成 Kaniko

在早期,我們的 pipeline 使用的是 Docker buildx。那個流程看起來大致是這樣:

before_script:
	- # 安裝 Docker 和 buildx plugin
	- curl -fsSLO https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz
	- # ... (省略詳細步驟)
script:
  - app=$(echo "$CI_COMMIT_TAG" | awk -F_ '{print $1}')
  - tag=$(echo "$CI_COMMIT_TAG" | awk -F_ '{print $2}')
  - docker buildx build --load --build-arg APP=$app \
    -t $ECR/example-project/$app:$tag \
    --cache-to type=registry,ref="$CI_REGISTRY_IMAGE:$app-cache",mode=max \
    --cache-from type=registry,ref="$CI_REGISTRY_IMAGE:$app-cache" .
  - docker push $ECR/example-project/$app:$tag

但這種方式每次建置都要花 5~10 分鐘,太耗時了。後來我們換成 Kaniko,時間直接降到 2 分鐘左右。這差異來自於:

  • buildx 需要 Docker daemon,在 CI runner 裡啟動 daemon 本身就要花很多時間。
  • Kaniko 是 daemonless 的,容器起來就能直接 build。
  • 在無特權環境下 Kaniko 也能正常運行,安全性更高。

不過在我寫這篇筆記的現在,Kaniko 已經進入 Archive 階段,因此我們預計在未來會改採用 buildkit 來作為我們 CI script 中建立 image 的工具。

ECR 的推送策略

我們的映像檔會推送到 AWS ECR,命名規則是:

example-project/<service>/<environment>:<tag>

例如:

  • example-project/backend/dev:1.2.3
  • example-project/frontend/staging:2.0.0

這樣的策略不只清楚,也和我們的 GitOps 流程緊密相連。因為版本號完全來自 Git tag,所以不用擔心手動標記導致版本漂移,保證了 CI → Registry → CD 的一致性。

ArgoCD Image Updater:串起 CD 的最後一塊拼圖

為了要更方便的 pull ECR 上最新的、指定的 image,我們一樣採用 Helm Manager 的架構來部署 ArgoCD Image Updater 這張 Helm chart。在 Day 13 的 App-of-Apps 架構裡,我們每個 Application 都能加上 Image Updater 的 annotation。例如:

metadata:
  name: testing-worker
  namespace: {{ .Values.project }}
  annotations:
    argocd-image-updater.argoproj.io/image-list: worker=123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/example-project/worker/testing
    argocd-image-updater.argoproj.io/worker.update-strategy: semver:~1.2
    argocd-image-updater.argoproj.io/worker.helm.image-name: deploy.image.name
    argocd-image-updater.argoproj.io/worker.helm.image-tag: deploy.image.tag
    argocd-image-updater.argoproj.io/write-back-method: argocd

這些 annotation 定義了:

  • 要監控的映像檔位置(ECR repo)
  • 版本更新策略(latest 或 semver)
  • 如何對應到 Helm valuesimage.nameimage.tag
  • 回寫方式(直接更新 ArgoCD,而不是 Git repo)

我們實際採用的策略是:

  • dev/testing 環境用 latest,方便快速測試。
  • prod 環境則用 semver,例如 semver:~1.2,代表只接受 1.2.x 的更新,不會自動升到 1.3。這樣能兼顧自動化與穩定性。

它不會打 commit 回 Git repo,因為對我們來說追蹤 image tag 是 image updater 的工作,我們沒有必要再在 GitLab 維護一份 image tag 的資訊。Git repo 保存 chart 與 values 的框架,而具體的映像檔版本由 pipeline 與 Image Updater 推進即可。

這裡有一個很重要的觀念:CI 負責產生映像檔,CD 負責消化映像檔。CI 的責任結束於「推到 ECR」,而 CD 的責任開始於「把最新的 image 套到 cluster」。這中間靠的就是 Image Updater,真正把 CI 與 CD 黏在一起。

在我們有正確設定 Image Updater 的情況下,我們就能看到 Application 的 image name 會被 Image Updater 改成我們在 Application 上所指定的值,以及 image tag 則會被 Image Updater 根據 annotation 選最新的 semver。

https://ithelp.ithome.com.tw/upload/images/20250921/2011966744SpQDFERf.jpg

GitOps 閉環:從 Code 到 Cluster

如果把整個流程攤開來,可以用文字畫出這樣一個閉環:

[Developer Commit & Tag]
        ↓
[GitLab CI Pipeline]
        ↓
[Kaniko Build Image]
        ↓
[Push to ECR Registry]
        ↓
[ArgoCD Image Updater 偵測新版本]
        ↓
[ArgoCD 套用到 Cluster]
        ↓
[Application Running]
        ↺
(下一次 Commit 再啟動新一輪)

這就是我們的 GitOps 閉環

  • 一切從 Git 開始,由 tag 驅動。
  • CI 負責驗證與產生映像檔。
  • CD(ArgoCD + Image Updater)自動吸收最新版本,更新到 cluster。
  • 整個流程透明、可追蹤,沒有任何一步需要人為操作。

小結

今天我們走完了 CI/CD 的另一半:

  • 說明了為什麼 buildx 被 Kaniko 取代,讓建置速度從 10 分鐘縮短到 2 分鐘。
  • 展示了我們的 ECR 推送策略,如何配合 Git tag 保持一致性。
  • 詳細解釋了 ArgoCD Image Updater,如何幫我們把新版本自動帶到叢集,並和 App-of-Apps 架構融合。
  • 最後用一個流程圖完整描繪了 GitOps 閉環

至此,我們已經不只是建好 infra,而是真正讓程式碼能夠「流動」起來。從 commit 到 deployment,全部自動化,這才是 GitOps 的精髓。


到這邊為止,我們已經算是完成了最基本可交付的 Kubernetes 叢集架構。從 Day 1 到 Day 21 介紹的內容都是我在作為 SRE 所學到的基本知識。至於從明天開始,我們會進一步討論架構上更進階,或者 Kubernetes 在網路底層的運作有關的介紹與研究。最後幾天我們一起加油吧 🪴


上一篇
[Day 20] 事件驅動的魔法:KEDA 讓 Pod 自己長大縮小
下一篇
[Day 22] 安全通道:Transit Gateway 與 GitLab Runner 串接的秘密
系列文
EKS in Practice:IaC × GitOps 實戰 30 天22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言